package com.boxman;

import java.awt.Component;
import java.awt.Graphics;
import java.awt.event.KeyEvent;
import java.io.File;
import java.io.FileInputStream;
import java.util.LinkedList;

// 游戏
class Game {
    // 回调
    public interface Callback {
        void onGameOver(boolean allOver);   // 游戏结束事件
        void onGotoLevel(int level);        // 进入关卡事件
        void onManMoved(int moveCount);     // 小人移动事件
    }

    /**
     * 移动结点。
     * @param box       被推动的箱子
     * @param direction 移动方向
     */
    record MoveNode(BaseGameObject box, int direction) {
        /**
         * 获取箱子。
         *
         * @return 箱子
         */
        @Override
        public BaseGameObject box() {
            return box;
        }

        /**
         * 获取方向。
         *
         * @return 方向
         */
        @Override
        public int direction() {
            return direction;
        }
    }
    
    // 状态
    public enum Status {
        END,    // 已结束
        PLAYING // 游戏中
    }

    private int mBoxCount;                              // 未到达目的地的箱子数
    private final Callback mCallback;                   // 回调
    private final Graphics mDrawer;                     // 绘图者
    private final Component mGameBoard;                 // 游戏区域
    private int mLevel;                                 // 关卡数
    private Man mMan;                                   // 小人
    private int mMoveCount;                             // 移动次数
    private final LinkedList<MoveNode> mMoveList;       // 所有移动结点
    private Status mStatus = Status.END;                // 状态
    public static BaseGameObject[][] sBaseGameObjects;  // 所有游戏物体
    
    /**
     * 构造方法。
     * @param callback  回调
     * @param drawer    绘图者
     * @param gameBoard 游戏区域
     */
    public Game(Callback callback, Graphics drawer, Component gameBoard) {
        // 初始化成员
        mBoxCount = 0;
        mDrawer = drawer;
        mCallback = callback;
        mGameBoard = gameBoard;
        mLevel = 1;
        mMoveCount = 0;
        mMoveList = new LinkedList<>();

        // 进入第1关
        this.enterLevel();
    }

    /**
     * 进入关卡。
     */
    public void enterLevel() {
        // 小人停止跳舞
        if (mMan != null) {
            mMan.stopDancing();
        }

        // 重绘游戏区域
        mDrawer.setColor(Const.GAME_BOARD_COLOR);
        mDrawer.fillRect(0, 0, Const.GAME_BOARD_WIDTH, Const.GAME_BOARD_HEIGHT);

        // 删除所有物体
        sBaseGameObjects = new BaseGameObject[Const.GAME_BOARD_GRID_COUNT_V][Const.GAME_BOARD_GRID_COUNT_H];

        // 删除所有移动结点
        mMoveList.clear();

        // 读取关卡文件
        final String levelFilePath = String.format(Const.LEVEL_FILE_PATH, mLevel);
        FileInputStream reader;
        try {
            final File file = new File(levelFilePath);
            reader = new FileInputStream(file);
        } catch (Exception e) {
            return;
        }

        // 创建所有物体
        int read = -1;
        BaseGameObject.Type type;
        mBoxCount = 0;
        for (int y = 0; y < Const.GAME_BOARD_GRID_COUNT_V; y++) {
            for (int x = 0; x < Const.GAME_BOARD_GRID_COUNT_H; x++) {
                try {
                    read = reader.read();
                } catch (Exception e) {
                    e.printStackTrace();
                }
                if (read == '0') {
                    continue;
                }
                if (read == '\r' || read == '\n') {
                    x--;
                    continue;
                }

                type = BaseGameObject.Type.values()[(read - '0')];

                // 创建箱子
                if (type == BaseGameObject.Type.BOX) {
                    sBaseGameObjects[y][x] = new Box(new BaseGameObject(mDrawer, BaseGameObject.Type.BACKGROUND, x, y), mDrawer, type, x, y);
                    mBoxCount++;
                    continue;
                }

                // 创建小人
                if (type == BaseGameObject.Type.MAN) {
                    sBaseGameObjects[y][x] = mMan = new Man(new BaseGameObject(mDrawer, BaseGameObject.Type.BACKGROUND, x, y),
                            mDrawer, mGameBoard, type, x, y);
                    continue;
                }

                // 创建背景、目的地、墙
                sBaseGameObjects[y][x] = new BaseGameObject(mDrawer, type, x, y);
            }
        }

        // 关闭文件
        try {
            reader.close();
        } catch (Exception e) {
            e.printStackTrace();
        }

        // 重置成员变量
        mMoveCount = 0;
        mStatus = Status.PLAYING;

        // 刷新游戏区域
        mGameBoard.repaint();

        // 发送通知
        mCallback.onGotoLevel(mLevel);
    }

    /**
     * 游戏结束。
     * @param allOver   全部通关则为 true，否则为 false
     */
    private void gameOver(boolean allOver) {
        // 将状态设置为已结束
        mStatus = Status.END;

        // 发送通知
        mCallback.onGameOver(allOver);
    }

    /**
     * 获取当前关卡数。
     * @return 当前关卡数
     */
    public int getLevel() {
        return mLevel;
    }
    
    /**
     * 进入指定的关卡。
     * @param level 关卡数
     */
    public void gotoLevel(int level) {
        if (level <= Const.LEVEL_COUNT) {
            mLevel = level;
            this.enterLevel();
        }
    }

    /**
     * 进入下一关。
     */
    private void gotoNextLevel() {
        if (mLevel < Const.LEVEL_COUNT) {
            mLevel++;
            this.enterLevel();
        }
    }

    /**
     * 判断是否胜利。
     * @return 胜利则返回 true，否则返回 false
     */
    private boolean isWon() {
        return mBoxCount == 0;
    }

    /**
     * 按键事件的响应方法。
     * @param key   按键
     */
    public void onKeyPressed(int key) {
        // 如果没在玩，则直接返回
        if (mStatus != Status.PLAYING) {
            return;
        }

        // 如果按的不是方向键，则直接返回
        if (key < KeyEvent.VK_LEFT || key > KeyEvent.VK_DOWN) {
            return;
        }
        
        // 移动小人。如果不能移动，则直接返回
        final BaseGameObject.MoveResult moveResult = new BaseGameObject.MoveResult();
        final BaseGameObject movedBox = mMan.move(key, moveResult);
        if (moveResult.result == BaseGameObject.MoveResult.Result.NO_MOVE) {
            return;
        }

        // 重绘游戏区域
        mGameBoard.repaint();

        // 增加移动次数
        mMoveCount++;

        // 发送通知
        mCallback.onManMoved(mMoveCount);

        // 判断是否胜利
        if (moveResult.result == BaseGameObject.MoveResult.Result.ARRIVED_ON_DEST) {
            mBoxCount--;
            if (this.isWon()) {
                final boolean allOver = mLevel == Const.LEVEL_COUNT;
                this.gameOver(allOver);
                if (!allOver) {
                    this.gotoNextLevel();
                }
                return;
            }
        } else if (moveResult.result == BaseGameObject.MoveResult.Result.LEFT_FROM_DEST) {
            mBoxCount++;
        }
        
        // 加本次移动加入列表
        mMoveList.addLast(new MoveNode(movedBox, key));
    }

    /**
     * 重玩本关。
     */
    public void restart() {
        this.gotoLevel(mLevel);
    }

    /**
     * 回退，即撤消上一次操作。
     */
    public void undo() {
        if (mMoveCount == 0) {
            return;
        }
        
        // 计算方向（方向与最后一次移动的方向相反）
        int direction = 0;
        final MoveNode moveListNode = mMoveList.removeLast();
        direction = switch (moveListNode.direction()) {
            case KeyEvent.VK_DOWN -> KeyEvent.VK_UP;
            case KeyEvent.VK_LEFT -> KeyEvent.VK_RIGHT;
            case KeyEvent.VK_RIGHT -> KeyEvent.VK_LEFT;
            case KeyEvent.VK_UP -> KeyEvent.VK_DOWN;
            default -> direction;
        };
        
        // 移动小人
        final BaseGameObject.MoveResult moveResult = new BaseGameObject.MoveResult();
        mMan.move(direction, moveResult);
        
        // 移动箱子
        final BaseGameObject box = moveListNode.box();
        if (box != null) {
            box.move(direction, moveResult);
            if (moveResult.result == BaseGameObject.MoveResult.Result.LEFT_FROM_DEST) {
                mBoxCount++;
            } else if (moveResult.result == BaseGameObject.MoveResult.Result.ARRIVED_ON_DEST) {
                mBoxCount--;
            }
        }
        
        // 发送通知
        mMoveCount--;
        mCallback.onManMoved(mMoveCount);
    }
}
